from datetime import datetime
from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union

from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
from pydantic.types import UUID4
from sqlalchemy.orm import Query, Session
from sqlalchemy.sql.expression import false
from sqlalchemy import or_, and_, desc, asc
from fastapi_pagination.ext.sqlalchemy import paginate

from app.models.archivable import Archivable
from app.models import User
from app.utils import ModelType, apply_changes
from app.models.TaskClassificationLibrary import TaskClassificationLibrary
from app.models.PersonalizedWordLibrary import PersonalizedWordLibrary

CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)

def paginate_key(query: Query, page: int, size: int) -> List[Any]:
    return query.offset((page - 1) * size).limit(size).all()

class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
    def __init__(self, model: Type[ModelType]):
        """
        CRUD object with default methods to Create, Read, Update, Delete (CRUD).

        **Parameters**

        * `model`: A SQLAlchemy model class
        * `schema`: A Pydantic model (schema) class
        """
        self.model = model

    def filter_archivable(self, query: Query, with_archived: Optional[bool] = False):
        if issubclass(self.model, Archivable) and not with_archived:
            query = query.filter(self.model.archived == false())
        return query

    def get(
        self, db: Session, id: UUID4, with_archived: Optional[bool] = False
    ) -> Optional[ModelType]:
        query: Query = db.query(self.model).filter(self.model.id == id)
        query = self.filter_archivable(query, with_archived)
        return query.first()

    def get_all(
        self, db: Session, with_archived: Optional[bool] = False
    ) -> List[ModelType]:
        query = db.query(self.model)
        query = self.filter_archivable(query, with_archived)
        return query.all()

    def get_multi(
            self, db: Session, *, skip: int = 0, limit: int = 100,
            with_archived: Optional[bool] = False
    ) -> List[ModelType]:
        query = db.query(self.model)
        query = self.filter_archivable(query, with_archived)
        return query.offset(skip).limit(limit).all()

    def get_multi_by_user(
            self, db: Session, *, user: User, model: ModelType, skip: int = 0, limit: int = 100,
            with_archived: Optional[bool] = False
    ) -> List[ModelType]:
        query = db.query(self.model).filter(model.user_id == user.id)
        query = self.filter_archivable(query, with_archived)
        return query.offset(skip).limit(limit).all()

    def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType:
        obj_in_data = jsonable_encoder(obj_in)
        db_obj = self.model(**obj_in_data)  # type: ignore
        apply_changes(db, db_obj)
        return db_obj

    def update(
        self,
        db: Session,
        *,
        db_obj: ModelType,
        obj_in: Union[UpdateSchemaType, Dict[str, Any]]
    ) -> ModelType:
        obj_data = jsonable_encoder(db_obj)
        if isinstance(obj_in, dict):
            update_data = obj_in
        else:
            update_data = obj_in.model_dump(exclude_unset=True)
        for field in obj_data:
            if field in update_data:
                setattr(db_obj, field, update_data[field])
        apply_changes(db, db_obj)
        return db_obj

    def remove(self, db: Session, obj: ModelType) -> ModelType:
        db.delete(obj)
        db.commit()
        return obj

    def archive(self, db: Session, obj: ModelType) -> ModelType:
        if issubclass(self.model, Archivable):
            obj.archived_at = datetime.utcnow()
            apply_changes(db, obj)
        return obj

    def unarchive(self, db: Session, obj: ModelType) -> ModelType:
        if issubclass(self.model, Archivable):
            obj.archived_at = None
            apply_changes(db, obj)
        return obj

    def create_with_user(self, db: Session, *, obj_in: CreateSchemaType, user: Optional[ModelType]) -> ModelType:
        db_obj = self.create(db, obj_in=obj_in)
        db_obj.user_id = user.id
        apply_changes(db, db_obj)
        return db_obj

    def order_by(self, query: Query, q: str = 'desc'):
        """
        默认按照创建时间倒序排序
        """
        if q == "desc":
            query = query.order_by(desc(self.model.created_at))
        else:
            query = query.order_by(asc(self.model.created_at))
        return query

    def get_paginate(
        self,
        db: Session,
        *,
        with_archived: Optional[bool] = False
    ) -> List[ModelType]:
        """
        使用 paginate 工具完成分页，区别于 get_multi 方法
        """
        query = db.query(self.model)
        query = self.filter_archivable(query, with_archived)
        return paginate(query)

    def get_paginate_by_user(
        self,
        db: Session,
        *,
        user: User,
        model: ModelType,
        with_archived: Optional[bool] = False
    ) -> List[ModelType]:
        """
        使用 paginate 工具完成分页，区别于 get_multi_by_user 方法
        """
        query = db.query(self.model).filter(model.user_id == user.id)
        query = self.filter_archivable(query, with_archived)
        return paginate(query)

    def get_multi_with_conditions(
            self, db: Session,
            *,
            conditions: list = None,
            order: str = 'desc',
            with_archived: Optional[bool] = False
    ) -> Dict:
        """
        分页 、多条件 查询
        """
        query = db.query(self.model).filter(and_(*conditions))
        query = self.filter_archivable(query, with_archived)
        query = self.order_by(query, order)
        return paginate(query)

    def get_by_name(
            self, db: Session, *, name: str, with_archived: Optional[bool] = False
    ) -> Optional[ModelType]:
        """
        指定 name 字段查询
        """
        query = db.query(self.model).filter(self.model.name == name)
        query = self.filter_archivable(query, with_archived)
        return query.first()

    def get_by_category_name(
            self, db: Session, *, name: str, with_archived: Optional[bool] = False
    ) -> Optional[ModelType]:
        """
        指定 name 字段查询
        """
        query = db.query(self.model).filter(self.model.category_name == name)
        query = self.filter_archivable(query, with_archived)
        return query.first()
    def get_key_paginate(
            self,
            db: Session,
            keyword: str,
            with_archived: Optional[bool] = False
    ) -> List[ModelType]:
        query = db.query(self.model).filter(
            self.model.category_name.ilike(f'%{keyword}%')
        )
        query = self.filter_archivable(query, with_archived)
        return paginate(query)

    def get_key_paginate_by_user(
            self,
            db: Session,
            user: User,
            keyword: str,
            with_archived: Optional[bool] = False
    ) -> List[ModelType]:
        query = db.query(self.model).filter(
            self.model.user_id == user.id,
            self.model.category_name.ilike(f'%{keyword}%')
        )
        query = self.filter_archivable(query, with_archived)
        return paginate(query)

    def get_by_keyword(
            self, db: Session, keyword: str
    ) -> List[TaskClassificationLibrary]:
        return db.query(TaskClassificationLibrary).filter(
            TaskClassificationLibrary.category_name.ilike(f'%{keyword}%')).all()
